package cn.jtcoding.web.framework.filter;

import cn.jtcoding.web.framework.http.MultiReadHttpServletRequest;
import cn.jtcoding.web.framework.http.MultiReadHttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 通过这个Filter可以将HttpServletRequest/HttpServletResponse进行封装使得getInputStream/getOutputStream能多次读取，
 * 并且可以在logback日志输出中增加MDC参数选项
 */

@Order(1)
@Component
@Slf4j
public class HttpLoggerFilter extends OncePerRequestFilter {
	private final static String REQUEST_ID = "requestID";
	private final static String URI = "uri";

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		try {
			String query = request.getQueryString() != null ? "?" + request.getQueryString() : "";
			MDC.put(REQUEST_ID, UUID.randomUUID().toString().replace("-", ""));
			MDC.put(URI, request.getRequestURI() + query);
			MultiReadHttpServletRequest multiReadRequest = this.wrapIfNecessary(request);
			MultiReadHttpServletResponse multiReadResponse = this.wrapIfNecessary(response);
			this.logRequest(multiReadRequest);

			filterChain.doFilter(multiReadRequest, multiReadResponse);
			multiReadResponse.setRemoteAddr(multiReadRequest.getRemoteAddr());
			multiReadResponse.setLocalAddr(multiReadRequest.getLocalAddr());
			this.logResponse(multiReadResponse);
		} finally {
			MDC.clear();
		}
	}

	private MultiReadHttpServletRequest wrapIfNecessary(HttpServletRequest request) throws IOException {
		MultiReadHttpServletRequest requestWraper;
		if (request instanceof MultiReadHttpServletRequest) {
			requestWraper = (MultiReadHttpServletRequest) request;
		} else {
			requestWraper = new MultiReadHttpServletRequest(request);
		}
		return requestWraper;
	}

	private MultiReadHttpServletResponse wrapIfNecessary(HttpServletResponse response) throws IOException {
		MultiReadHttpServletResponse responseWraper;
		if (response instanceof MultiReadHttpServletResponse) {
			responseWraper = (MultiReadHttpServletResponse) response;
		} else {
			responseWraper = new MultiReadHttpServletResponse(response);
		}
		return responseWraper;
	}

	private void logRequest(MultiReadHttpServletRequest multiReadRequest) throws IOException {
		HttpServletRequest request = multiReadRequest.getOriginalRequest();
		if (!RequestMethod.GET.toString().equals(request.getMethod())) {
			String body = "";
			if (!isMultipart(multiReadRequest.getContentType())
					&& !isBinaryContent(multiReadRequest.getContentType())) {
				body = multiReadRequest.getRequestBody();
			}
			log.info("[{} ==> {}] Method: {}, Body: {}, Header: {}, Param: {}", request.getRemoteAddr(), request.getLocalAddr(),
					request.getMethod(), body, this.getHeadersInfo(request), this.getParamsInfo(request));
		} else {
			log.info("[{} ==> {}] Method : {}, Header: {}", request.getRemoteAddr(), request.getLocalAddr(),
					request.getMethod(), this.getHeadersInfo(request));
		}
	}

	private void logResponse(MultiReadHttpServletResponse multiReadResponse) throws IOException {
		String body = "";
		if (!isMultipart(multiReadResponse.getContentType()) && !isBinaryContent(multiReadResponse.getContentType())) {
			body = multiReadResponse.getResponseBody();
		}
		log.info("[{} <== {}] ContentType: {}, Body: {}", multiReadResponse.getRemoteAddr(),
				multiReadResponse.getLocalAddr(), multiReadResponse.getContentType(), body);
	}

	private Map<String, String> getHeadersInfo(HttpServletRequest request) {
		Map<String, String> map = new HashMap<String, String>();
		Enumeration<String> headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String key = headerNames.nextElement();
			String value = request.getHeader(key);
			map.put(key, value);
		}
		return map;
	}
	
	private Map<String, String> getParamsInfo(HttpServletRequest request) {
		Map<String, String> map = new HashMap<String, String>();
		Enumeration<String> parameterNames = request.getParameterNames();
		while (parameterNames.hasMoreElements()) {
			String key = parameterNames.nextElement();
			String value = request.getParameter(key);
			map.put(key, value);
		}
		return map;
	}

	private boolean isBinaryContent(String contentType) {
		if (contentType == null) {
			return false;
		}
		return contentType.contains("image") || contentType.contains("video") || contentType.contains("audio")
				|| contentType.contains("font-woff2") || contentType.contains("pdf") || contentType.contains("officedocument");
	}

	private boolean isMultipart(String contentType) {
		return contentType != null && contentType.contains("multipart/form-data");
	}
}
